﻿//-----------------------------------------------------------------------
// <author>Paul Long</author>
//-----------------------------------------------------------------------

namespace NetworkAddresses
{
    using System;
    using System.Globalization;
    using System.Runtime.InteropServices;
    using Microsoft.NetworkMonitor;
    using TopUserExceptions;

    /// <summary>
    /// Maintains ethernet address information.
    /// </summary>
    internal class MachineAddress : Address
    {
        /// <summary>
        /// Holds the raw address.
        /// </summary>
        private byte[] address;

        /// <summary>
        /// Initializes a new instance of the MachineAddress class.
        /// Retrieves the Ethernet information from the frame given the parsed frame and Data field ID.
        /// </summary>
        /// <param name="parsedFrame">Parsed Frame to retreived address from.</param>
        /// <param name="id">ID returned from NmAddField</param>
        public unsafe MachineAddress(IntPtr parsedFrame, uint id)
        {
            NmParsedFieldInfo parsedDataFieldInfo = new NmParsedFieldInfo();
            parsedDataFieldInfo.Size = (ushort)Marshal.SizeOf(typeof(NmParsedFieldInfo));
            uint errno = NetmonAPI.NmGetParsedFieldInfo(parsedFrame, id, (uint)0, ref parsedDataFieldInfo);
            if (errno != 0)
            {
                throw new ParsingException("NmGetParsedField(...) failed, errno = " + errno);
            }

            byte[] buffer = null;

            uint length = parsedDataFieldInfo.ValueBufferLength;
            if (length > 0)
            {
                byte* rightOperand = (byte*)Marshal.AllocCoTaskMem((int)length);
                uint actuallength = 0;

                errno = NetmonAPI.NmGetFieldInBuffer(parsedFrame, id, length, rightOperand, out actuallength);

                if (errno != 0)
                {
                    throw new ParsingException("NmGetFieldInBuffer(...) failed, errno = " + errno);
                }

                if (actuallength < (uint)length)
                {
                    Console.WriteLine(
                        "Warning: NmGetFieldInBuffer(...) actual copied length:{0} different with buffer length:{1}",
                        actuallength, 
                        length);
                }

                buffer = new byte[actuallength];
                Marshal.Copy((IntPtr)rightOperand, buffer, 0, buffer.Length);

                Marshal.FreeCoTaskMem((IntPtr)rightOperand);
            }

            if (buffer != null)
            {
                this.address = buffer;
                this.Display = String.Format(
                                        CultureInfo.CurrentCulture, 
                                        "{0,00:X2}-{1,00:X2}-{2,00:X2}-{3,00:X2}-{4,00:X2}-{5,00:X2}", 
                                        this.address[0],
                                        this.address[1],
                                        this.address[2],
                                        this.address[3],
                                        this.address[4],
                                        this.address[5]);
            }
            else
            {
                this.address = null;
            }
        }

        /// <summary>
        /// Compares two Ethenet Address objects.
        /// </summary>
        /// <param name="leftOperand">The first ethernet address.</param>
        /// <param name="rightOperand">The second ethernet address.</param>
        /// <returns>True if equal</returns>
        public static bool operator ==(MachineAddress leftOperand, MachineAddress rightOperand)
        {
            if ((object)leftOperand == null || (object)rightOperand == null)
            {
                return false;
            }

            if (leftOperand.address == null || rightOperand.address == null)
            {
                return false;
            }

            ulong ap, bp;

            ap = (ulong)leftOperand.address[0] | (ulong)leftOperand.address[1] << 8 | (ulong)leftOperand.address[2] << 16 | (ulong)leftOperand.address[3] << 24 |
                (ulong)leftOperand.address[4] << 32 | (ulong)leftOperand.address[5] << 40;
            bp = (ulong)rightOperand.address[0] | (ulong)rightOperand.address[1] << 8 | (ulong)rightOperand.address[2] << 16 | (ulong)rightOperand.address[3] << 24 |
                (ulong)rightOperand.address[4] << 32 | (ulong)rightOperand.address[5] << 40;

            if (ap == bp)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Compares two ethernet address for inequality.
        /// </summary>
        /// <param name="leftOperand">The first ethernet address.</param>
        /// <param name="rightOperand">The second ethernet address.</param>
        /// <returns>True if not equal</returns>
        public static bool operator !=(MachineAddress leftOperand, MachineAddress rightOperand)
        {
            if ((object)leftOperand == null || (object)rightOperand == null)
            {
                return false;
            }

            if (leftOperand.address == null || rightOperand.address == null)
            {
                return false;
            }

            ulong ap, bp;

            ap = (ulong)leftOperand.address[0] | (ulong)leftOperand.address[1] << 8 | (ulong)leftOperand.address[2] << 16 | (ulong)leftOperand.address[3] << 24 |
                (ulong)leftOperand.address[4] << 32 | (ulong)leftOperand.address[5] << 40;
            bp = (ulong)rightOperand.address[0] | (ulong)rightOperand.address[1] << 8 | (ulong)rightOperand.address[2] << 16 | (ulong)rightOperand.address[3] << 24 |
                (ulong)rightOperand.address[4] << 32 | (ulong)rightOperand.address[5] << 40;

            if (ap != bp)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Compares two ethernet address for greater than.
        /// </summary>
        /// <param name="leftOperand">The first ethernet address.</param>
        /// <param name="rightOperand">The second ethernet address.</param>
        /// <returns>True if greater than.</returns>
        public static bool operator >(MachineAddress leftOperand, MachineAddress rightOperand)
        {
            if ((object)leftOperand == null || (object)rightOperand == null)
            {
                return false;
            }

            if (leftOperand.address == null || rightOperand.address == null)
            {
                return false;
            }

            ulong ap, bp;
            ap = (ulong)leftOperand.address[0] | (ulong)leftOperand.address[1] << 8 | (ulong)leftOperand.address[2] << 16 | (ulong)leftOperand.address[3] << 24 |
                (ulong)leftOperand.address[4] << 32 | (ulong)leftOperand.address[5] << 40;
            bp = (ulong)rightOperand.address[0] | (ulong)rightOperand.address[1] << 8 | (ulong)rightOperand.address[2] << 16 | (ulong)rightOperand.address[3] << 24 |
                (ulong)rightOperand.address[4] << 32 | (ulong)rightOperand.address[5] << 40;
            if (ap > bp)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Compares two ethernet address for less than.
        /// </summary>
        /// <param name="leftOperand">The first ethernet address.</param>
        /// <param name="rightOperand">The second ethernet address.</param>
        /// <returns>True if less than.</returns>
        public static bool operator <(MachineAddress leftOperand, MachineAddress rightOperand)
        {
            if ((object)leftOperand == null || (object)rightOperand == null)
            {
                return false;
            }

            if (leftOperand.address == null || rightOperand.address == null)
            {
                return false;
            }

            ulong ap, bp;
            ap = (ulong)leftOperand.address[0] | (ulong)leftOperand.address[1] << 8 | (ulong)leftOperand.address[2] << 16 | (ulong)leftOperand.address[3] << 24 |
                (ulong)leftOperand.address[4] << 32 | (ulong)leftOperand.address[5] << 40;
            bp = (ulong)rightOperand.address[0] | (ulong)rightOperand.address[1] << 8 | (ulong)rightOperand.address[2] << 16 | (ulong)rightOperand.address[3] << 24 |
                (ulong)rightOperand.address[4] << 32 | (ulong)rightOperand.address[5] << 40;

            if (ap < bp)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Returns if the addess is valid or not.
        /// </summary>
        /// <returns>Returns true if the address is valid.</returns>
        public bool IsValid()
        {
            if (this.address != null)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Comparison for equality
        /// </summary>
        /// <param name="obj">Object to compare</param>
        /// <returns>True if equal</returns>
        public override bool Equals(object obj)
        {
            return (MachineAddress)obj == this;
        }

        /// <summary>
        /// Gets the hash code for equality..
        /// </summary>
        /// <returns>Returns hash code</returns>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }
}
